#include "hooks.h"
#include "Extern_global_Variable.h"

struct sTJumpRecord
{
	Byte Command;
	PtrUInt Offset;
};
typedef sTJumpRecord* PJumpRecord;

static const Byte ASM_CALL_FAR = 0xE8;
static const Byte ASM_JUMP_FAR = 0xE9;

#if (WIN32)
Dword __cdecl OpCodeLength(Pointer Address)
{
	Dword result;
const Byte O_UNIQUE = 0;
const Byte O_PREFIX = 1;
const Byte O_IMM8 = 2;
const Byte O_IMM16 = 3;
const Byte O_IMM24 = 4;
const Byte O_IMM32 = 5;
const Byte O_IMM48 = 6;
const Byte O_MODRM = 7;
const Byte O_MODRM8 = 8;
const Byte O_MODRM32 = 9;
const Byte O_EXTENDED = 10;
const Byte O_WEIRD = 11;
const Byte O_ERROR = 12;
__asm
{
  pushad
  cld
  xor edx, edx
  mov esi, Address
  mov ebp, esp
  push 1097F71Ch
  push 0F71C6780h
  push 17389718h
  push 101CB718h
  push 17302C17h
  push 18173017h
  push 0F715F547h
  push 4C103748h
  push 272CE7F7h
  push 0F7AC6087h
  push 1C121C52h
  push 7C10871Ch
  push 201C701Ch
  push 4767602Bh
  push 20211011h
  push 40121625h
  push 82872022h
  push 47201220h
  push 13101419h
  push 18271013h
  push 28858260h
  push 15124045h
  push 5016A0C7h
  push 28191812h
  push 0F2401812h
  push 19154127h
  push 50F0F011h
  mov ecx, 15124710h
  push ecx
  push 11151247h
  push 10111512h
  push 47101115h
  mov eax, 12472015h
  push eax
  push eax
  push 12471A10h
  add cl, 10h
  push ecx
  sub cl, 20h
  push ecx
  xor ecx, ecx
  dec ecx
__ps:
  inc  ecx
  mov  edi, esp
__go:
  lodsb
  mov  bh, al
__ft:
  mov  ah, [edi]
  inc  edi
  shr  ah, 4
  sub  al, ah
  jnc  __ft
  mov al, [edi-1]
  and al, 0Fh
  cmp  al, O_ERROR
  jnz  __i7
  pop edx
  not edx
__i7:
  inc edx
  cmp al, O_UNIQUE
  jz __t_exit
  cmp al, O_PREFIX
  jz __ps
  add  edi, 51h
  cmp  al, O_EXTENDED
  jz   __go
  mov edi, [ebp+((1+8)*4)+4]
__i6:
  inc  edx
  cmp  al, O_IMM8
  jz   __t_exit
  cmp  al, O_MODRM
  jz   __t_modrm
  cmp  al, O_WEIRD
  jz   __t_weird
__i5:
  inc  edx
  cmp  al, O_IMM16
  jz   __t_exit
  cmp  al, O_MODRM8
  jz   __t_modrm
__i4:
  inc  edx
  cmp  al, O_IMM24
  jz   __t_exit
__i3:
  inc  edx
__i2:
  inc  edx
  pushad
  mov  al, 0x66
  repnz scasb
  popad
  jnz  __c32
__d2:
  dec  edx
  dec  edx
__c32:
  cmp  al, O_MODRM32
  jz   __t_modrm
  sub  al, O_IMM32
  jz   __t_imm32
__i1:
  inc  edx
  __t_exit:
  jmp __ASMEnded
__t_modrm:
  lodsb
  mov  ah, al
  shr  al, 7
  jb   __prmk
  jz   __prm
  add  dl, 4
  pushad
  mov  al, 0x67
  repnz scasb
  popad
  jnz  __prm
__d3:  sub  dl, 3
  dec  al
__prmk:jnz  __t_exit
  inc  edx
  inc  eax
__prm:
  and  ah, 0x111b
  pushad
  mov  al, 0x67
  repnz scasb
  popad
  jz   __prm67chk
  cmp  ah, 0x4
  jz   __prmsib
  cmp  ah, 0x5
  jnz  __t_exit
__prm5chk:
  dec  al
  jz   __t_exit
__i42: add  dl, 4
  jmp  __t_exit
__prm67chk:
  cmp  ax, 0x600
  jnz  __t_exit
  inc  edx
  jmp  __i1
__prmsib:
  cmp  al, 0x0
  jnz  __i1
  lodsb
  and  al, 0x111b
  sub  al, 0x5
  jnz  __i1
  inc  edx
  jmp  __i42
__t_weird:
  test byte ptr [esi], 0x111000b
  jnz  __t_modrm
  mov  al, O_MODRM8
  shr  bh, 1
  adc  al, 0
  jmp  __i5
__t_imm32:
  sub  bh, 0xA0
  cmp  bh, 0x4
  jae  __d2
  pushad
  mov  al, 0x67
  repnz scasb
  popad
  jnz  __chk66t
__d4:  dec  edx
  dec  edx
__chk66t:
  pushad
  mov  al, 0x66
  repnz scasb
  popad
  jz   __i1
  jnz  __d2
__ASMEnded:
  mov esp, ebp
  mov [result+(9*4)], edx
  popad
  };
  return result;
};
#else
Dword function OpCodeLength(Pointer Address)
{

};
#endif

PtrUInt CalcJumpOffset(Pointer dst, Pointer src)
{
  return PtrUInt((PtrUInt)dst - (PtrUInt)src - sizeof(sTJumpRecord));
};

Pointer GetJumpDestination(Pointer src, PtrUInt Offset)
{
	return (Pointer)((Dword)src + sizeof(sTJumpRecord) + (Dword)Offset);
};

Boolean WriteJumpAtAddress(Handle Process, Pointer src, Pointer dst,
	PBytes OriginalBytes)
{
	sTJumpRecord JumpRecord;

	if (OriginalBytes != NULL)
	{
		*OriginalBytes = new Byte[sizeof(sTJumpRecord)];
		ReadProcessMemory(Process, src, &(*OriginalBytes)[0], sizeof(*OriginalBytes)/sizeof(*OriginalBytes[0]), NULL);
	};

	JumpRecord.Command = ASM_JUMP_FAR;
	JumpRecord.Offset = CalcJumpOffset(dst,src);
	return WriteProcessMemory(Process, src, &JumpRecord, sizeof(sTJumpRecord), NULL);
};

Boolean WriteCallAtAddress(Handle Process, Pointer src, Pointer dst)
{
  sTJumpRecord JumpRecord;

  JumpRecord.Command = ASM_CALL_FAR;
  JumpRecord.Offset = CalcJumpOffset(dst,src);
  return WriteProcessMemory(Process, src, &JumpRecord, sizeof(sTJumpRecord), NULL);
};

Boolean WriteCallAtAddress(Handle Process, Pointer src, Pointer dst, Pointer &OldDst)
{
	OldDst = GetJumpDestination(src, (PtrUInt)*((PDword)src+1));
	return WriteCallAtAddress(Process, src, dst);
};

Boolean Hook(Pointer OrigFunc, Pointer HookFunc, Pointer NewFunc, PBytes OldBytes)
{
	Boolean Result;
	Integer vvi;
	Dword OldProtect;
	SizeUInt TotalSize;
	SizeUInt OpCodeLen;
	Pointer DestAddr;
	PJumpRecord pJumpRec;
	Pointer NewFuncMem;

	Result = FALSE;

	if ((OrigFunc == NULL) || (HookFunc == NULL)) return Result;

	NewFuncMem = VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (NewFuncMem == NULL) return Result;

	TotalSize = 0;
	OpCodeLen = 0;
	for (vvi = 1; vvi <= 5; vvi++)
	{
		OpCodeLen = OpCodeLength((Pointer)((SizeUInt)OrigFunc+TotalSize));
		memcpy((Pointer)((SizeUInt)NewFuncMem+TotalSize), (Pointer)((SizeUInt)OrigFunc+TotalSize), OpCodeLen);

		pJumpRec = (PJumpRecord)((SizeUInt)NewFuncMem + TotalSize);
		if (pJumpRec->Command == ASM_JUMP_FAR || pJumpRec->Command == ASM_CALL_FAR)
		{
			DestAddr = GetJumpDestination((Pointer)((SizeUInt)OrigFunc+TotalSize), pJumpRec->Offset);
			pJumpRec->Offset = CalcJumpOffset(DestAddr, (Pointer)((SizeUInt)NewFuncMem+TotalSize));
		};

		TotalSize += OpCodeLen;
		if (TotalSize >= sizeof(sTJumpRecord)) break;
	};

	if (OldBytes != NULL)
	{
		*OldBytes = new Byte[TotalSize];
		memcpy(&(*OldBytes)[0], OrigFunc, TotalSize);
	};

	if (!VirtualProtect(OrigFunc, TotalSize, PAGE_EXECUTE_READWRITE, (PDWORD)&OldProtect)) return Result;

	if (WriteJumpAtAddress(GetCurrentProcess(),(Pointer)((SizeUInt)NewFuncMem+TotalSize),(Pointer)((SizeUInt)OrigFunc+TotalSize),NULL) == FALSE) return Result;
    FlushInstructionCache(GetCurrentProcess(), NewFuncMem, TotalSize + sizeof(sTJumpRecord));

    if (&NewFunc != NULL) NewFunc = NewFuncMem;

	if (WriteJumpAtAddress(GetCurrentProcess(),OrigFunc,HookFunc,NULL) == FALSE) return Result;
    FlushInstructionCache(GetCurrentProcess(), OrigFunc, TotalSize);

	VirtualProtect(OrigFunc, TotalSize, OldProtect, (PDWORD)&OldProtect);

	Result = TRUE;
	return Result;
};

procedure UnHook(Pointer OrigFunc, Pointer NewFunc, PBytes OldBytes)
{
	if (OldBytes != NULL)
	{
		WriteProcessMemory(
			GetCurrentProcess(),
			OrigFunc,
			&(*OldBytes)[0],
			sizeof(*OldBytes)/sizeof(*OldBytes)[0],
			NULL
		);

		FlushInstructionCache(
			GetCurrentProcess(),
			&OrigFunc,
			sizeof(*OldBytes)/sizeof(*OldBytes)[0]
		);
	};

	VirtualFree(NewFunc, 0, MEM_FREE);
};
